/****************************************************************************
 * net/bluetooth/bt_conn.c
 * Bluetooth connection handling.
 *
 *   Copyright (C) 2018 Gregory Nutt. All rights reserved.
 *   Author: Gregory Nutt <gnutt@nuttx.org>
 *
 * Ported from the Intel/Zephyr arduino101_firmware_source-v1.tar package
 * where the code was released with a compatible 3-clause BSD license:
 *
 *   Copyright (c) 2016, Intel Corporation
 *   All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * 1. Redistributions of source code must retain the above copyright notice,
 *    this list of conditions and the following disclaimer.
 *
 * 2. Redistributions in binary form must reproduce the above copyright notice,
 *    this list of conditions and the following disclaimer in the documentation
 *     and/or other materials provided with the distribution.
 *
 * 3. Neither the name of the copyright holder nor the names of its
 *    contributors may be used to endorse or promote products derived from this
 *    software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 *
 ****************************************************************************/

/****************************************************************************
 * Included Files
 ****************************************************************************/

#include <tinyara/config.h>

#include <stdbool.h>
#include <stdlib.h>
#include <fcntl.h>
#include <string.h>
#include <errno.h>
#include <debug.h>

#include <tinyara/kthread.h>
#include <tinyara/bluetooth/iob/iob.h>
#include <tinyara/bluetooth/bt_hci.h>
#include <tinyara/bluetooth/bt_core.h>

#include "bt_atomic.h"
#include "bt_queue.h"
#include "bt_hcicore.h"
#include "bt_conn.h"
#include "bt_l2cap.h"
#include "bt_keys.h"
#include "bt_smp.h"

/*! @brief Computes the number of elements in an array. */
#if !defined(ARRAY_SIZE)
#define ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0]))
#endif

/****************************************************************************
 * Private Types
 ****************************************************************************/

struct bt_conn_handoff_s {
	sem_t sync_sem;
	FAR struct bt_conn_s *conn;
};

/****************************************************************************
 * Private Data
 ****************************************************************************/

static struct bt_conn_s g_conns[CONFIG_BLUETOOTH_MAX_CONN];
static struct bt_conn_handoff_s g_conn_handoff = {
	{0},
	NULL
};

/****************************************************************************
 * Private Functions
 ****************************************************************************/

static const char *state2str(enum bt_conn_state_e state)
{
	switch (state) {
	case BT_CONN_DISCONNECTED:
		return "disconnected";

	case BT_CONN_CONNECT_SCAN:
		return "connect-scan";

	case BT_CONN_CONNECT:
		return "connect";

	case BT_CONN_CONNECTED:
		return "connected";

	case BT_CONN_DISCONNECT:
		return "disconnect";

	default:
		return "(unknown)";
	}
}

static void bt_conn_reset_rx_state(FAR struct bt_conn_s *conn)
{
	if (!conn->rx_len) {
		return;
	}

	bt_buf_release(conn->rx);
	conn->rx = NULL;
	conn->rx_len = 0;
}

static int conn_tx_kthread(int argc, FAR char *argv[])
{
	FAR struct bt_conn_s *conn;
	FAR struct bt_buf_s *buf;
	int ret;

	/* Get the connection instance */

	conn = g_conn_handoff.conn;
	DEBUGASSERT(conn != NULL);
	sem_post(&g_conn_handoff.sync_sem);

	nvdbg("Started for handle %u\n", conn->handle);

	while (conn->state == BT_CONN_CONNECTED) {
		/* Wait until the controller can accept ACL packets */

		nvdbg("calling sem_wait\n");

		do {
			ret = sem_wait(&g_btdev.le_pkts_sem);
		} while (errno == EINTR);

		DEBUGASSERT(ret == OK);

		/* Check for disconnection */

		if (conn->state != BT_CONN_CONNECTED) {
			sem_post(&g_btdev.le_pkts_sem);
			break;
		}

		/* Get next ACL packet for connection */

		ret = bt_queue_receive(conn->tx_queue, &buf);
		DEBUGASSERT(ret >= 0 && buf != NULL);
		UNUSED(ret);

		if (conn->state != BT_CONN_CONNECTED) {
			sem_post(&g_btdev.le_pkts_sem);
			bt_buf_release(buf);
			break;
		}

		nvdbg("passing buf %p len %u to driver\n", buf, buf->len);
		g_btdev.btdev->send(g_btdev.btdev, buf);
		bt_buf_release(buf);
	}

	nvdbg("handle %u disconnected - cleaning up\n", conn->handle);

	/* Give back any allocated buffers */

	do {
		buf = NULL;
		ret = bt_queue_receive(conn->tx_queue, &buf);
		if (ret >= 0) {
			DEBUGASSERT(buf != NULL);
			bt_buf_release(buf);
		}
	} while (ret >= OK);

	bt_conn_reset_rx_state(conn);

	nvdbg("handle %u exiting\n", conn->handle);
	bt_conn_release(conn);
	return EXIT_SUCCESS;
}

int bt_hci_disconnect(FAR struct bt_conn_s *conn, uint8_t reason)
{
	FAR struct bt_buf_s *buf;
	FAR struct bt_hci_cp_disconnect_s *disconn;
	int err;

	buf = bt_hci_cmd_create(BT_HCI_OP_DISCONNECT, sizeof(*disconn));
	if (!buf) {
		return -ENOBUFS;
	}

	disconn = bt_buf_extend(buf, sizeof(*disconn));
	disconn->handle = BT_HOST2LE16(conn->handle);
	disconn->reason = reason;

	err = bt_hci_cmd_send(BT_HCI_OP_DISCONNECT, buf);
	if (err) {
		return err;
	}

	bt_conn_set_state(conn, BT_CONN_DISCONNECT);
	return 0;
}

int bt_hci_connect_le_cancel(FAR struct bt_conn_s *conn)
{
	int err;

	err = bt_hci_cmd_send(BT_HCI_OP_LE_CREATE_CONN_CANCEL, NULL);
	if (err) {
		return err;
	}

	return 0;
}

/****************************************************************************
 * Public Functions
 ****************************************************************************/

/****************************************************************************
 * Name: bt_conn_receive
 *
 * Description:
 *   Receive packets from the HCI core on a registered connection.
 *
 * Input Parameters:
 *   conn  - The registered connection
 *   buf   - The buffer structure containing the packet
 *   flags - Packet boundary flags
 *
 * Returned Value:
 *   None
 *
 ****************************************************************************/

void bt_conn_receive(FAR struct bt_conn_s *conn, FAR struct bt_buf_s *buf, uint8_t flags)
{
	FAR struct bt_l2cap_hdr_s *hdr;
	uint16_t len;

	nvdbg("handle %u len %u flags %02x\n", conn->handle, buf->len, flags);

	/* Check packet boundary flags */

	switch (flags) {
	case 0x02:
		/* First packet */

		hdr = (void *)buf->data;
		len = BT_LE162HOST(hdr->len);

		nvdbg("First, len %u final %u\n", buf->len, len);

		if (conn->rx_len) {
			ndbg("ERROR: Unexpected first L2CAP frame\n");
			bt_conn_reset_rx_state(conn);
		}

		conn->rx_len = (sizeof(*hdr) + len) - buf->len;
		nvdbg("rx_len %u\n", conn->rx_len);
		if (conn->rx_len) {
			conn->rx = buf;
			return;
		}

		break;

	case 0x01:
		/* Continuation */

		if (!conn->rx_len) {
			ndbg("ERROR: Unexpected L2CAP continuation\n");
			bt_conn_reset_rx_state(conn);
			bt_buf_release(buf);
			return;
		}

		if (buf->len > conn->rx_len) {
			ndbg("ERROR: L2CAP data overflow\n");
			bt_conn_reset_rx_state(conn);
			bt_buf_release(buf);
			return;
		}

		nvdbg("Cont, len %u rx_len %u\n", buf->len, conn->rx_len);

		if (buf->len > bt_buf_tailroom(conn->rx)) {
			ndbg("ERROR: Not enough buffer space for L2CAP data\n");
			bt_conn_reset_rx_state(conn);
			bt_buf_release(buf);
			return;
		}

		memcpy(bt_buf_extend(conn->rx, buf->len), buf->data, buf->len);
		conn->rx_len -= buf->len;
		bt_buf_release(buf);

		if (conn->rx_len) {
			return;
		}

		buf = conn->rx;
		conn->rx = NULL;
		conn->rx_len = 0;

		break;

	default:
		ndbg("ERROR: Unexpected ACL flags (0x%02x)\n", flags);
		bt_conn_reset_rx_state(conn);
		bt_buf_release(buf);
		return;
	}

	hdr = (void *)buf->data;
	len = BT_LE162HOST(hdr->len);

	if (sizeof(*hdr) + len != buf->len) {
		ndbg("ERROR: ACL len mismatch (%u != %u)\n", len, buf->len);
		bt_buf_release(buf);
		return;
	}

	nvdbg("Successfully parsed %u byte L2CAP packet\n", buf->len);

	bt_l2cap_receive(conn, buf);
}

/****************************************************************************
 * Name: bt_conn_send
 *
 * Description:
 *   Send data over a connection
 *
 * Input Parameters:
 *   conn  - The registered connection
 *   buf   - The buffer structure containing the packet to be sent
 *
 * Returned Value:
 *   None
 *
 ****************************************************************************/

void bt_conn_send(FAR struct bt_conn_s *conn, FAR struct bt_buf_s *buf)
{
	FAR struct bt_hci_acl_hdr_s *hdr;
	sq_queue_t fraglist;
	uint16_t len;
	uint16_t remaining = buf->len;
	FAR uint8_t *ptr;

	DEBUGASSERT(conn != NULL && buf != NULL);
	nvdbg("conn handle %u buf len %u\n", conn->handle, buf->len);

	if (conn->state != BT_CONN_CONNECTED) {
		ndbg("ERROR: not connected!\n");
		return;
	}

	len = remaining;
	if (len > g_btdev.le_mtu) {
		len = g_btdev.le_mtu;
	}

	hdr = bt_buf_provide(buf, sizeof(*hdr));
	hdr->handle = BT_HOST2LE16(conn->handle);
	hdr->len = BT_HOST2LE16(len);

	buf->len -= remaining - len;
	ptr = bt_buf_tail(buf);

	/* Add the fragment to the end of the list */

	sq_addlast((FAR sq_entry_t *) buf, &fraglist);
	remaining -= len;

	while (remaining) {
		buf = bt_l2cap_create_pdu(conn);

		len = remaining;
		if (len < g_btdev.le_mtu) {
			len = g_btdev.le_mtu;
		}

		/* Copy from original buffer */

		memcpy(bt_buf_extend(buf, len), ptr, len);
		ptr += len;

		hdr = bt_buf_provide(buf, sizeof(*hdr));
		hdr->handle = BT_HOST2LE16(conn->handle | (1 << 12));
		hdr->len = BT_HOST2LE16(len);

		/* Add the fragment to the end of the list */

		sq_addlast((FAR sq_entry_t *) buf, &fraglist);
		remaining -= len;
	}

	/* Then send each fragment in the correct order */

	while ((buf = (FAR struct bt_buf_s *)sq_remfirst(&fraglist)) != NULL) {
		bt_queue_send(conn->tx_queue, buf, BT_NORMAL_PRIO);
	}
}

/****************************************************************************
 * Name: bt_conn_add
 *
 * Description:
 *   Add a new connection
 *
 * Input Parameters:
 *   peer - The address of the Bluetooth peer
 *   role - Either BT_HCI_ROLE_MASTER or BT_HCI_ROLE_SLAVE
 *
 * Returned Value:
 *   A reference to the new connection structure is returned on success.
 *
 ****************************************************************************/

FAR struct bt_conn_s *bt_conn_add(FAR const bt_addr_le_t *peer, uint8_t role)
{
	FAR struct bt_conn_s *conn = NULL;
	int i;

	for (i = 0; i < CONFIG_BLUETOOTH_MAX_CONN; i++) {
		if (!bt_addr_le_cmp(&g_conns[i].dst, BT_ADDR_LE_ANY)) {
			conn = &g_conns[i];
			break;
		}
	}

	if (!conn) {
		return NULL;
	}

	memset(conn, 0, sizeof(*conn));

	bt_atomic_set(&conn->ref, 1);
	conn->role = role;
	bt_addr_le_copy(&conn->dst, peer);

	return conn;
}

static struct bt_conn_s *conn_new(void)
{
	FAR struct bt_conn_s *conn = NULL;
	int i;

	for (i = 0; i < ARRAY_SIZE(g_conns); i++) {
		if (!bt_atomic_get(&g_conns[i].ref)) {
			conn = &g_conns[i];
			break;
		}
	}

	if (!conn) {
		return NULL;
	}

	(void)memset(conn, 0, sizeof(*conn));

	bt_atomic_set(&conn->ref, 1);

	return conn;
}

struct bt_conn_s *bt_conn_add_le(const bt_addr_le_t *peer)
{
	FAR struct bt_conn_s *conn = conn_new();

	if (!conn) {
		return NULL;
	}

	bt_addr_le_copy(&conn->dst, peer);

	/* if CONFIG_BT_SMP, then below two sec parameter required */
	conn->sec_level = BT_SECURITY_LOW;
	conn->required_sec_level = BT_SECURITY_LOW;

	conn->type = BT_CONN_TYPE_LE;
	conn->le.interval_min = BT_GAP_INIT_CONN_INT_MIN;
	conn->le.interval_max = BT_GAP_INIT_CONN_INT_MAX;

	return conn;
}

/****************************************************************************
 * Name: bt_conn_set_state
 *
 * Description:
 *   Set connection object in certain state and perform actions related to
 *   state change.
 *
 * Input Parameters:
 *   conn - The connection whose state will be changed.
 *   state - The new state of the connection.
 *
 * Returned Value:
 *   None
 *
 ****************************************************************************/

void bt_conn_set_state(FAR struct bt_conn_s *conn, enum bt_conn_state_e state)
{
	enum bt_conn_state_e old_state;

	nvdbg("%s -> %s\n", state2str(conn->state), state2str(state));

	if (conn->state == state) {
		nwdbg("no transition\n");
		return;
	}

	old_state = conn->state;
	conn->state = state;

	/* Take a reference for the first state transition after bt_conn_add() and
	 * keep it until reaching DISCONNECTED again.
	 */

	if (old_state == BT_CONN_DISCONNECTED) {
		bt_conn_addref(conn);
	}

	switch (conn->state) {
	case BT_CONN_CONNECTED: {
		pid_t pid;
		int ret;

		ret = bt_queue_open(BT_CONN_TX, O_RDWR | O_CREAT, CONFIG_BLUETOOTH_TXCONN_NMSGS, &conn->tx_queue);
		DEBUGASSERT(ret >= 0 && g_btdev.tx_queue != 0);
		UNUSED(ret);

		/* Get exclusive access to the handoff structure.  The count will be
		 * zero when we complete this.
		 */

		do {
			ret = sem_wait(&g_conn_handoff.sync_sem);
		} while (errno == EINTR);

		DEBUGASSERT(ret == OK);

		/* Start the Tx connection kernel thread */

		g_conn_handoff.conn = bt_conn_addref(conn);
		pid = kernel_thread("BT Conn Tx", CONFIG_BLUETOOTH_TXCONN_PRIORITY, CONFIG_BLUETOOTH_TXCONN_STACKSIZE, conn_tx_kthread, NULL);
		DEBUGASSERT(pid > 0);
		UNUSED(pid);

		/* Take the semaphore again.  This will force us to wait with the
		 * sem_count at -1.  It will be zero again when we continue.
		 */

		do {
			ret = sem_wait(&g_conn_handoff.sync_sem);
		} while (errno == EINTR);

		DEBUGASSERT(ret == OK);
		sem_post(&g_conn_handoff.sync_sem);
	}
	break;

	case BT_CONN_DISCONNECTED:
		/* Send dummy buffer to wake up and stop the Tx thread for states where it
		 * was running.
		 */

		if (old_state == BT_CONN_CONNECTED || old_state == BT_CONN_DISCONNECT) {
			bt_queue_send(conn->tx_queue, bt_buf_alloc(BT_DUMMY, NULL, 0), BT_NORMAL_PRIO);
		}

		/* Release the reference we took for the very first state transition. */

		bt_conn_release(conn);
		break;

	case BT_CONN_CONNECT_SCAN:
	case BT_CONN_CONNECT:
	case BT_CONN_DISCONNECT:
		break;

	default:
		nwdbg("no valid (%u) state was set\n", state);
		break;
	}
}

/****************************************************************************
 * Name: bt_conn_lookup_handle
 *
 * Description:
 *   Look up an existing connection
 *
 * Input Parameters:
 *   handle - The handle to be used to perform the lookup
 *
 * Returned Value:
 *   A reference to the connection state instance is returned on success.
 *   NULL is returned if the connection is not found.  On success, the
 *   caller gets a new reference to the connection object which must be
 *   released with bt_conn_release() once done using the connection.
 *
 ****************************************************************************/

FAR struct bt_conn_s *bt_conn_lookup_handle(uint16_t handle)
{
	int i;

	for (i = 0; i < CONFIG_BLUETOOTH_MAX_CONN; i++) {
		if (!bt_atomic_get(&g_conns[i].ref)) {
			continue;
		}

		/* We only care about connections with a valid handle */
		if (g_conns[i].state != BT_CONN_CONNECTED && g_conns[i].state != BT_CONN_DISCONNECT) {
			continue;
		}

		if (g_conns[i].handle == handle) {
			return bt_conn_addref(&g_conns[i]);
		}
	}

	return NULL;
}

/****************************************************************************
 * Name: bt_conn_lookup_addr_le_internal
 *
 * Description:
 *   Look up an existing connection based on the remote address.
 *
 * Input Parameters:
 *   peer - Remote address.
 *
 * Returned Value:
 *   A reference to the connection state instance is returned on success.
 *   NULL is returned if the connection is not found.  On success, the
 *   caller gets a new reference to the connection object which must be
 *   released with bt_conn_release() once done using the connection.
 *
 ****************************************************************************/

FAR struct bt_conn_s *bt_conn_lookup_addr_le_internal(FAR const bt_addr_le_t *peer)
{
	int i;

	for (i = 0; i < CONFIG_BLUETOOTH_MAX_CONN; i++) {
		if (!bt_addr_le_cmp(peer, &g_conns[i].dst)) {
			return bt_conn_addref(&g_conns[i]);
		}
	}

	return NULL;
}

struct bt_conn_s *bt_conn_lookup_addr_le_id(uint8_t id, const bt_addr_le_t *peer)
{
	int i;

	for (i = 0; i < ARRAY_SIZE(g_conns); i++) {
		if (!bt_atomic_get(&g_conns[i].ref)) {
			continue;
		}

		if (g_conns[i].type != BT_CONN_TYPE_LE) {
			continue;
		}

		if (g_conns[i].id == id && !bt_conn_addr_le_cmp(&g_conns[i], peer)) {

			return bt_conn_addref(&g_conns[i]);
		}
	}

	return NULL;
}

/****************************************************************************
 * Name: bt_conn_lookup_state
 *
 * Description:
 *   Look up a connection state.  For BT_ADDR_LE_ANY, returns the first
 *   connection with the specific state
 *
 * Input Parameters:
 *   peer  - The peer address to match
 *   state - The connection state to match
 *
 * Returned Value:
 *   A reference to the connection state instance is returned on success.
 *   NULL is returned if the connection is not found.  On success, the
 *   caller gets a new reference to the connection object which must be
 *   released with bt_conn_release() once done using the connection.
 *
 ****************************************************************************/

FAR struct bt_conn_s *bt_conn_lookup_state(FAR const bt_addr_le_t *peer, enum bt_conn_state_e state)
{
	int i;

	for (i = 0; i < CONFIG_BLUETOOTH_MAX_CONN; i++) {
		if (!bt_addr_le_cmp(&g_conns[i].dst, BT_ADDR_LE_ANY)) {
			continue;
		}

		if (bt_addr_le_cmp(peer, BT_ADDR_LE_ANY) && bt_addr_le_cmp(peer, &g_conns[i].dst)) {
			continue;
		}

		if (g_conns[i].state == state) {
			return bt_conn_addref(&g_conns[i]);
		}
	}

	return NULL;
}

int bt_conn_addr_le_cmp(const struct bt_conn_s *conn, const bt_addr_le_t *peer)
{
	/* Check against conn dst address as it may be the identity address */
	if (!bt_addr_le_cmp(peer, &conn->dst)) {
		return 0;
	}

	/* Check against initial connection address */
	if (conn->role == BT_HCI_ROLE_MASTER) {
		return bt_addr_le_cmp(peer, &conn->le.resp_addr);
	}

	return bt_addr_le_cmp(peer, &conn->le.init_addr);
}

struct bt_conn_s *bt_conn_lookup_state_le(const bt_addr_le_t *peer, const enum bt_conn_state_e state)
{
	int i;

	for (i = 0; i < CONFIG_BLUETOOTH_MAX_CONN; i++) {
		if (!bt_atomic_get(&g_conns[i].ref)) {
			continue;
		}

		if (g_conns[i].type != BT_CONN_TYPE_LE) {
			continue;
		}

		if (peer && bt_conn_addr_le_cmp(&g_conns[i], peer)) {
			continue;
		}

		if (g_conns[i].state == state) {
			return bt_conn_addref(&g_conns[i]);
		}
	}

	return NULL;
}

/****************************************************************************
 * Name: bt_conn_addref
 *
 * Description:
 *   Increment the reference count of a connection object.
 *
 * Input Parameters:
 *   conn - Connection object.
 *
 * Returned Value:
 *   Connection object with incremented reference count.
 *
 ****************************************************************************/

FAR struct bt_conn_s *bt_conn_addref(FAR struct bt_conn_s *conn)
{
	bt_atomic_incr(&conn->ref);

	nvdbg("handle %u ref %u\n", conn->handle, bt_atomic_get(&conn->ref));

	return conn;
}

/****************************************************************************
 * Name: bt_conn_release
 *
 * Description:
 *   Decrement the reference count of a connection object.
 *
 * Input Parameters:
 *   conn - Connection object.
 *
 * Returned Value:
 *   None
 *
 ****************************************************************************/

void bt_conn_release(FAR struct bt_conn_s *conn)
{
	bt_atomic_t old_ref;

	old_ref = bt_atomic_decr(&conn->ref);

	nvdbg("handle %u ref %u\n", conn->handle, bt_atomic_get(&conn->ref));

	if (old_ref > 1) {
		return;
	}

	bt_addr_le_copy(&conn->dst, BT_ADDR_LE_ANY);
}

struct bt_conn_s *bt_conn_relref(FAR struct bt_conn_s *conn)
{
	bt_atomic_decr(&conn->ref);

	nvdbg("handle %u ref %u\n", conn->handle, bt_atomic_get(&conn->ref));

	return conn;
}

/****************************************************************************
 * Name: bt_conn_get_dst_internal
 *
 * Description:
 *   Get destination (peer) address of a connection.
 *
 * Input Parameters:
 *   conn - Connection object.
 *
 * Returned Value:
 *   Destination address.
 *
 ****************************************************************************/

FAR const bt_addr_le_t *bt_conn_get_dst_internal(FAR const struct bt_conn_s *conn)
{
	return &conn->dst;
}

/****************************************************************************
 * Name: bt_conn_security_internal
 *
 * Description:
 *   This function enable security (encryption) for a connection. If device is
 *   already paired with sufficiently strong key encryption will be enabled. If
 *   link is already encrypted with sufficiently strong key this function does
 *   nothing.
 *
 *   If device is not paired pairing will be initiated. If device is paired and
 *   keys are too weak but input output capabilities allow for strong enough keys
 *   pairing will be initiated.
 *
 *   This function may return error if required level of security is not possible
 *   to achieve due to local or remote device limitation (eg input output
 *   capabilities).
 *
 * Input Parameters:
 *   conn - Connection object.
 *   sec  - Requested security level.
 *
 * Returned Value:
 *   0 on success or negative error
 *
 ****************************************************************************/

int bt_conn_security_internal(FAR struct bt_conn_s *conn, bt_security_t sec)
{
	FAR struct bt_keys_s *keys;

	if (conn->state != BT_CONN_CONNECTED) {
		return -ENOTCONN;
	}

	/* Nothing to do */

	if (sec == BT_SECURITY_LOW) {
		return 0;
	}

	/* For now we only support JustWorks */

	if (sec > BT_SECURITY_MEDIUM) {
		return -EINVAL;
	}

	if (conn->encrypt) {
		return 0;
	}

	if (conn->role == BT_HCI_ROLE_SLAVE) {
		return bt_smp_send_security_req(conn);
	}

	keys = bt_keys_find(BT_KEYS_LTK, &conn->dst);
	if (keys) {
		return bt_conn_le_start_encryption(conn, keys->ltk.rand, keys->ltk.ediv, keys->ltk.val);
	}

	return bt_smp_send_pairing_req(conn);
}

/****************************************************************************
 * Name:bt_conn_set_auto_conn
 *
 * Description:
 *   This function enables/disables automatic connection initiation.
 *   Every time the device looses the connection with peer, this connection
 *   will be re-established if connectible advertisement from peer is
 *   received.
 *
 * Input Parameters:
 *   conn      - Existing connection object.
 *   auto_conn - boolean value. If true, auto connect is enabled, if false,
 *               auto connect is disabled.
 *
 * Returned Value:
 *   None
 *
 ****************************************************************************/

void bt_conn_set_auto_conn(FAR struct bt_conn_s *conn, bool auto_conn)
{
	if (auto_conn) {
		bt_atomic_setbit(conn->flags, BT_CONN_AUTO_CONNECT);
	} else {
		bt_atomic_clrbit(conn->flags, BT_CONN_AUTO_CONNECT);
	}
}

/****************************************************************************
 * Name: bt_conn_disconnect_internal
 *
 * Description:
 *   Disconnect an active connection with the specified reason code or cancel
 *   pending outgoing connection.
 *
 * Input Parameters:
 *   conn   - Connection to disconnect.
 *   reason - Reason code for the disconnection.
 *
 * Returned Value:
 *   Zero on success or (negative) error code on failure.
 *
 ****************************************************************************/

int bt_conn_disconnect_internal(FAR struct bt_conn_s *conn, uint8_t reason)
{
	/* Disconnection is initiated by us, so auto connection shall be disabled.
	 * Otherwise the passive scan would be enabled and we could send LE Create
	 * Connection as soon as the remote starts advertising.
	 */

	bt_conn_set_auto_conn(conn, false);

	switch (conn->state) {
	case BT_CONN_CONNECT_SCAN:
		bt_conn_set_state(conn, BT_CONN_DISCONNECTED);
		bt_le_scan_update();
		return 0;

	case BT_CONN_CONNECT:
		return bt_hci_connect_le_cancel(conn);

	case BT_CONN_CONNECTED:
		return bt_hci_disconnect(conn, reason);

	case BT_CONN_DISCONNECT:
		return 0;

	case BT_CONN_DISCONNECTED:
	default:
		return -ENOTCONN;
	}
}

/****************************************************************************
 * Name: bt_conn_create_le_internal
 *
 * Description:
 *  Allows initiate new LE link to remote peer using its address.
 *  Returns a new reference that the caller is responsible for managing.
 *
 * Input Parameters:
 *   peer - Remote address.
 *
 * Returned Value:
 *   Valid connection object on success or NULL otherwise.
 *
 ****************************************************************************/

FAR struct bt_conn_s *bt_conn_create_le_internal(FAR const bt_addr_le_t *peer)
{
	FAR struct bt_conn_s *conn;

	/* First check if this connection exists and that it is in a proper
	 * state.
	 */

	conn = bt_conn_lookup_addr_le_internal(peer);
	if (conn != NULL) {
		switch (conn->state) {
		case BT_CONN_CONNECT_SCAN:
		case BT_CONN_CONNECT:
		case BT_CONN_CONNECTED:
			return conn;

		default:
			bt_conn_release(conn);
			return NULL;
		}
	}

	/* No.. the connection does not exist.  Create it assuming MASTER role
	 * and put it in the BT_CONNECT_SCAN state.
	 */

	conn = bt_conn_add(peer, BT_HCI_ROLE_MASTER);
	if (!conn) {
		return NULL;
	}

	bt_conn_set_state(conn, BT_CONN_CONNECT_SCAN);
	bt_le_scan_update();
	return conn;
}

/****************************************************************************
 * Name: bt_conn_le_start_encryption
 *
 * Description:
 *   See the HCI start encryption command.
 *
 *   NOTE: rand and ediv should be in BT order.
 *
 * Input Parameters:
 *   conn       - The connection to send the command on.
 *   rand, ediv - Values to use for the encryption key
 *   ltk        -
 *
 * Returned Value:
 *   Zero is returned on success; a negated errno value is returned on any
 *   failure.
 *
 ****************************************************************************/

int bt_conn_le_start_encryption(FAR struct bt_conn_s *conn, uint64_t rand, uint16_t ediv, FAR const uint8_t *ltk)
{
	FAR struct bt_hci_cp_le_start_encryption_s *cp;
	FAR struct bt_buf_s *buf;

	buf = bt_hci_cmd_create(BT_HCI_OP_LE_START_ENCRYPTION, sizeof(*cp));
	if (!buf) {
		return -ENOBUFS;
	}

	cp = bt_buf_extend(buf, sizeof(*cp));
	cp->handle = BT_HOST2LE16(conn->handle);
	cp->rand = rand;
	cp->ediv = ediv;
	memcpy(cp->ltk, ltk, sizeof(cp->ltk));

	return bt_hci_cmd_send_sync(BT_HCI_OP_LE_START_ENCRYPTION, buf, NULL);
}

int bt_conn_le_conn_update(FAR struct bt_conn_s *conn, uint16_t min, uint16_t max, uint16_t latency, uint16_t timeout)
{
	FAR struct hci_cp_le_conn_update_s *conn_update;
	FAR struct bt_buf_s *buf;

	buf = bt_hci_cmd_create(BT_HCI_OP_LE_CONN_UPDATE, sizeof(*conn_update));
	if (!buf) {
		return -ENOBUFS;
	}

	conn_update = bt_buf_extend(buf, sizeof(*conn_update));
	memset(conn_update, 0, sizeof(*conn_update));
	conn_update->handle = BT_HOST2LE16(conn->handle);
	conn_update->conn_interval_min = BT_HOST2LE16(min);
	conn_update->conn_interval_max = BT_HOST2LE16(max);
	conn_update->conn_latency = BT_HOST2LE16(latency);
	conn_update->supervision_timeout = BT_HOST2LE16(timeout);

	return bt_hci_cmd_send(BT_HCI_OP_LE_CONN_UPDATE, buf);
}

void bt_conn_set_param_le(struct bt_conn_s *conn, const struct bt_le_conn_param *param)
{
	conn->le.interval_min = param->interval_min;
	conn->le.interval_max = param->interval_max;
	conn->le.latency = param->latency;
	conn->le.timeout = param->timeout;
}

bool bt_le_conn_params_valid(const struct bt_le_conn_param *param)
{
	/* All limits according to BT Core spec 5.0 [Vol 2, Part E, 7.8.12] */

	if (param->interval_min > param->interval_max || param->interval_min < 6 || param->interval_max > 3200) {
		return false;
	}

	if (param->latency > 499) {
		return false;
	}

	if (param->timeout < 10 || param->timeout > 3200 || ((param->timeout * 4U) <= ((1 + param->latency) * param->interval_max))) {
		return false;
	}

	return true;
}

void bt_conn_handoff_init(void)
{
	/* Initialize a semaphore for connection handoff */

	sem_init(&g_conn_handoff.sync_sem, 0, 1);
}
